/*
 Copyright (C) 2007 - Michael Opitz
 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "gmail-list.h"
#include "gmail.h"

#include <pthread.h>
#include <glib.h>

#define LIST_PRIVATE(o) \
	  (G_TYPE_INSTANCE_GET_PRIVATE ((o), GMAIL_TYPE_LIST, GmailListPrivate))

pthread_mutex_t gmail_list_login_mutex = PTHREAD_MUTEX_INITIALIZER;

#define LOG(str)			  (printf("%s\n", (str)))

/* 
 * set/get accounts
 */
enum {
	PROP_ACCOUNTS = 1,
};

/*
 * we have a gmail-list-changed signal.
 */
enum {
	CHANGED = 1,
	LAST_SIGNAL
};

struct _GmailListPrivate
{
	/** 
	 * A list of GmailAccounts
	 */
	GList *accounts;
	/** 
	 * The pointer to the posix-thread.
	 */
	pthread_t login_thread;
	/** 
	 * This list of GmailAccounts is used by the login-thread.
	 */
	GList **thread_account; 
	/** 
	 * indicates whether the object is disposed or not.
	 */
	gboolean is_disposed;
};

static guint signals[LAST_SIGNAL];
static GObjectClass *parent_class = NULL;
static GType type = 0;

/* GObject init function */
static void
gmail_list_init (GmailList *self);
/** 
 * gmail_list_copy_back:
 * 
 * @acc: is a pointer to a GmailAccount
 * @thrd_acc: is a pointer to a list of GmailAccounts
 *
 * This function copies the thrd_acc-list which's contents where modified by the
 * login-thread back to the main GmailList.
 *
 */
void
gmail_list_copy_back (GmailAccount *acc, GList *thrd_acc);
/** 
 * gmail_list_login_thread:
 * 
 * @ptr: is a GmailList which contains deep-copies of the GmailAccounts.
 *
 * This function is executed in a seperate thread and logs the GmailAccounts in
 * and fetches the MessageInfo's.
 *
 */
void*
gmail_list_login_thread (void *ptr);
/** 
 * gmail_list_do_login:
 * 
 * @acc: a pointer to a GmailAccount
 * @user_data:
 *
 * This function calls the GmailAccount->login function for the corresponding
 * account.
 *
 */
void 
gmail_list_do_login (GmailAccount *acc, gpointer user_data);
/** 
 * gmail_list_deep_copy:
 * 
 * @acc: a pointer to a GmailAccount
 * @user_data: a GList**
 *
 * Creates a deep copy of the GmailAccount and appends it to the user_data.
 *
 */
void
gmail_list_deep_copy (GmailAccount *acc, gpointer user_data);
/** 
 * gmail_list_find_account_impl:
 * 
 * @a: a pointer to a GmailAccount.
 * @b: a pointer to a string which contains a email address. 
 * 
 * This function finds a GmailAccount with the corresponding email address.
 *
 */
gint
gmail_list_find_account_impl (GmailAccount *a, char *b);

static void
gmail_list_class_init (GmailListClass *klass);

GType 
gmail_list_register_type (GTypeModule *module)
{
	const GTypeInfo info = {
		sizeof (GmailListClass),
		NULL, 		/* base_init */
		NULL,		/* base_finalize */
		(GClassInitFunc) gmail_list_class_init,
		NULL, 		/* class_finalize */
		NULL, 		/* class_data */
		sizeof (GmailList), 
		0, 		/* n_preallocs */
		(GInstanceInitFunc) gmail_list_init
	};
	type = g_type_module_register_type (module, 
					    G_TYPE_OBJECT, 
					    "GmailList",
					    &info, 0);
	return type;
}

GType 
gmail_list_get_type ()
{
	return type;
}

static void
gmail_list_get_property (GObject *object, guint property_id,
			 GValue *value, GParamSpec *pspec)
{
	GmailList *self = GMAIL_LIST (object);
	GList *rtrn = NULL;
	switch  (property_id) {
	case PROP_ACCOUNTS:
		/* the user should free the returned list, but not the elements
		 * within it! */
		rtrn = g_list_copy (self->priv->accounts);
		g_value_set_pointer (value, rtrn);		
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object,
						   property_id, pspec);
		break;
	}
}

static void
gmail_list_set_property (GObject *object, guint property_id,
			 const GValue *value, GParamSpec *pspec)
{
	GmailList *self = GMAIL_LIST (object);
	GList *ptr = NULL;
	switch (property_id) {
	case PROP_ACCOUNTS:
		/* note: user must free given list! */
		ptr = g_value_get_pointer (value);
		/* referencing foreign objects... */
		g_list_foreach (ptr, (GFunc) g_object_ref, NULL);
		/* freeing our accounts... */ 
		g_list_foreach (self->priv->accounts, (GFunc) g_object_ref, NULL);
		g_list_free (self->priv->accounts);

		self->priv->accounts = g_list_copy (ptr);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object,
						   property_id, pspec);
		break;
	}
}

static void
gmail_list_dispose (GObject *object)
{
	LOG ("gmail-list dispose");
	GmailList *self = GMAIL_LIST (object);
	self->priv->is_disposed = TRUE;

	gmail_account_save (self->priv->accounts);
	g_list_foreach (self->priv->accounts, (GFunc) g_object_unref, NULL);
	if (self->priv->login_thread != 0) {
		pthread_cancel (self->priv->login_thread);
		self->priv->login_thread = 0;
	}
	if(*self->priv->thread_account != NULL) {
		g_list_foreach (*self->priv->thread_account, 
			        (GFunc) g_object_unref, NULL);
		g_list_free (*self->priv->thread_account);
		*self->priv->thread_account = NULL;
	}
	if (G_OBJECT_CLASS (parent_class)->dispose)
		G_OBJECT_CLASS (parent_class)->dispose (object);
}

static void
gmail_list_finalize (GObject *object)
{
	GmailList *self = GMAIL_LIST (object);
	g_list_free (self->priv->accounts);
	if (*self->priv->thread_account) 
		g_list_free (*self->priv->thread_account);
	g_free (self->priv->thread_account);
	G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
gmail_list_class_init (GmailListClass *klass)
{
	GParamSpec *pspec;
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	parent_class = g_type_class_peek_parent (klass);

	g_type_class_add_private (klass, sizeof (GmailListPrivate));

	object_class->get_property = gmail_list_get_property;
	object_class->set_property = gmail_list_set_property;
	object_class->dispose = gmail_list_dispose;
	object_class->finalize = gmail_list_finalize;

	/* creating the properties */
	pspec = g_param_spec_pointer ("accounts", "Gmail Accounts",
				      "Gmail Accounts which are in a GList",
				      G_PARAM_READWRITE);
	g_object_class_install_property (object_class,
					 PROP_ACCOUNTS, 
					 pspec);

	/* creating the signals */
	signals[CHANGED] = g_signal_new ("gmail-list-changed",
					 G_TYPE_FROM_CLASS(object_class),
					 G_SIGNAL_RUN_FIRST, 
					 G_STRUCT_OFFSET (GmailListClass, 
						 	  changed),
					 NULL, NULL, 
					 g_cclosure_marshal_VOID__VOID,
					 G_TYPE_NONE, 0);
}

static void
gmail_list_init (GmailList *self)
{
	self->priv = LIST_PRIVATE (self);
	self->priv->accounts = gmail_account_load ();
	self->priv->thread_account = (GList**) malloc (sizeof (GList*));
	(*self->priv->thread_account) = NULL;
	self->priv->login_thread = 0;
	self->priv->is_disposed = FALSE;
}

gint
gmail_list_find_account_impl (GmailAccount *a, char *b)
{
	char *tmp;
	g_object_get (a, "mail", &tmp, NULL);
	if (b != NULL) 
		return strcmp (tmp, b);
	return 1;
}

GmailAccount *
gmail_list_find_account (GmailList *list, char *mail)
{
	if (list->priv->is_disposed) return NULL;
	GList *tmp = g_list_find_custom (list->priv->accounts, mail, 
					 (GCompareFunc) 
					 gmail_list_find_account_impl);
	if(tmp == NULL) return NULL;
	GmailAccount *acc = (GmailAccount*) tmp->data;
	/* g_list_free(tmp); */
	return acc;
}

void
gmail_list_remove_account_by_mail (GmailList *list, char *mail)
{
	if (list->priv->is_disposed) return;
	GmailAccount *acc = gmail_list_find_account (list, mail);
	if(acc != NULL) {
		gmail_list_remove_account (list, acc);
	}
}

void
gmail_list_remove_account (GmailList *list, GmailAccount *acc)
{
	if (list->priv->is_disposed) return;
	list->priv->accounts = g_list_remove (list->priv->accounts, acc);
	g_signal_emit (list, signals[CHANGED], 0);	/* fire listeners */
	g_object_unref (acc);
}

void
gmail_list_append_account (GmailList *list, GmailAccount *acc)
{
	if (list->priv->is_disposed) return;
	list->priv->accounts = g_list_append (list->priv->accounts, acc);
	g_object_ref (acc);
	g_signal_emit (list, signals[CHANGED], 0);	/* fire listeners */
}

void 
gmail_list_emit_changed(GmailList *list)
{
	if (list->priv->is_disposed) return;
	g_signal_emit (list, signals[CHANGED], 0);	/* fire listeners */
}

GmailList *
gmail_list_new (void)
{
	return g_object_new (GMAIL_TYPE_LIST, NULL);
}

void
gmail_list_foreach (GmailList *list, GFunc fun, void *user_data)
{
	if (list->priv->is_disposed) return;
	g_list_foreach (list->priv->accounts, fun, user_data);
}

void
gmail_list_deep_copy (GmailAccount *acc, gpointer user_data)
{
	GList **lst = (GList**) user_data;
	g_assert(lst != NULL && acc != NULL);
	char *mail;
	char *pw;
	GList *msgs;
	g_object_get (acc, "mail", &mail, "password", &pw, "message_infos",
			&msgs, NULL);
	*lst = g_list_append (*lst, g_object_new (GMAIL_ACCOUNT_TYPE, 
						"mail", mail, 
						"password", pw, 
						"message_infos", msgs,
						NULL));
	/* FIXME: don't copy message_infos! */
	g_free (mail);
	g_free (pw);
}

void 
gmail_list_do_login (GmailAccount *acc, gpointer user_data)
{
	g_return_if_fail (acc != NULL);
	GMAIL_ACCOUNT_GET_CLASS (acc)->login (acc);
}

void *
gmail_list_login_thread (void *ptr)
{
	pthread_mutex_lock (&gmail_list_login_mutex);
	GList **accounts = (GList**) ((GmailList*)ptr)->priv->thread_account;
	g_return_val_if_fail (accounts != NULL, NULL);
	g_list_foreach (*accounts, (GFunc) gmail_list_do_login, NULL);
	pthread_mutex_unlock (&gmail_list_login_mutex);
	g_idle_add (gmail_list_check, (GmailList*) ptr);
	return NULL;
}

void 
gmail_list_login (GmailList *list)
{
	if (list->priv->is_disposed) return;

	if (list->priv->login_thread != 0) {
		return;
	}

	if (*list->priv->thread_account != NULL) {
		g_list_foreach (*list->priv->thread_account,
			       (GFunc) g_object_unref, NULL);
		g_list_free (*list->priv->thread_account);
		*list->priv->thread_account = NULL;
	}

	/* copy our account-data */
	g_list_foreach (list->priv->accounts, 
		        (GFunc) gmail_list_deep_copy, 
		        list->priv->thread_account);
	pthread_create (&list->priv->login_thread, NULL, 
			 gmail_list_login_thread,
			 list);
}

void
gmail_list_copy_back (GmailAccount *acc, GList *thrd_acc)
{
	g_return_if_fail (acc != NULL && thrd_acc != NULL);
	char *mail;
	g_object_get (G_OBJECT (acc), "mail", &mail, NULL);
	GList *tmp = g_list_find_custom (thrd_acc, 
					 mail,
					 (GCompareFunc) 
					  gmail_list_find_account_impl);
	if (tmp != NULL) {
		GmailAccount *to_copy = (GmailAccount*) tmp->data;
		GList *msgs_cpy;
		g_object_get (G_OBJECT (to_copy), "message_infos", &msgs_cpy,
			      NULL);
		g_object_set (G_OBJECT (acc), "message_infos", msgs_cpy, NULL);
	}
}

gboolean
gmail_list_check (GmailList *list)
{
	if (list->priv->is_disposed) return;
	
	int val = pthread_mutex_trylock (&gmail_list_login_mutex);
	if (val == EBUSY) {
		return;
	}

	/* user logged in! */
	if (*list->priv->thread_account != NULL) {
		/* copy data back */
		g_list_foreach (list->priv->accounts,
			        (GFunc) gmail_list_copy_back,
			        (*list->priv->thread_account));
		g_list_foreach (*list->priv->thread_account,
				(GFunc) g_object_unref, NULL);
		g_list_free (*list->priv->thread_account);
		(*list->priv->thread_account) = NULL;
		list->priv->login_thread = 0;

		gmail_list_emit_changed (list);
	}
	pthread_mutex_unlock (&gmail_list_login_mutex);
	return FALSE;
}

